// Evol functions.
// Authors:
//    Travolta
// Description:
//    Moving npc utility functions (graph-based)
// Variables:
//    none


function	script	initmovegraph	{
    deletearray getvariableofnpc(.movegraphcmd$, strnpcinfo(3));
    deletearray getvariableofnpc(.movegraphlabels$, strnpcinfo(3));
    deletearray getvariableofnpc(.movegraphweight, strnpcinfo(3));
    deletearray getvariableofnpc(.movegraphflags, strnpcinfo(3));
    deletearray getvariableofnpc(.movepos_y1, strnpcinfo(3));
    deletearray getvariableofnpc(.movepos_x1, strnpcinfo(3));
    deletearray getvariableofnpc(.movepos_x2, strnpcinfo(3));
    deletearray getvariableofnpc(.movepos_y2, strnpcinfo(3));
    .@cnt = 0;

    for (.@f = 0; .@f < getargcount();)
    {
        set getvariableofnpc(.movegraphlabels$[.@cnt], strnpcinfo(3)), getarg(.@f++);
        set getvariableofnpc(.movepos_x1[.@cnt], strnpcinfo(3)), getarg(.@f++);
        set getvariableofnpc(.movepos_y1[.@cnt], strnpcinfo(3)), getarg(.@f++);
        if (!isstr(getarg(.@f, "label")))
        {
            set getvariableofnpc(.movepos_x2[.@cnt], strnpcinfo(3)), getarg(.@f++);
            set getvariableofnpc(.movepos_y2[.@cnt], strnpcinfo(3)), getarg(.@f++);
        }
        .@cnt ++;
    }
    return;
}


function	script	findmovegraphlabel	{
    if (!getargcount())
    {
        debugmes "findmovegraphlabel: no argument";
        return -1;
    }
    if (!isstr(getarg(0)))
    {
        debugmes "findmovegraphlabel: need string argument";
        return -1;
    }

    .@arg$ = getarg(0);
    for (.@i = 0; .@i < getarraysize(getvariableofnpc(.movegraphlabels$, strnpcinfo(3))); .@i++)
    {
        if (getvariableofnpc(.movegraphlabels$[.@i], strnpcinfo(3)) == .@arg$)
            return .@i;
    }

    npcdebug "findmovegraphlabel: label not found: " + getarg(0);
    return -1;
}


/* setmovegraphcmd(fromPositionLabel,toPositionLabel[,moveChanceWeight[,moveFlags]],postCommand, ...);
 * This function manipulates NPC moving graph. Before calling it, make sure
 * `initmovegraph' was called. The function accepts 3-5 parameters (many times):
 * fromPositionLabel, toPositionLabel -- starting and ending position of NPC move
 * moveChanceWeight -- positive integer, represents the cnance of moving in given direction. (optional)
 * moveFlags -- if .mg_flags & moveFlags != 0, move is possible. (optional)
 * postCommand -- either "moveon" (start moving to next location straight after arriving from 
 *                fromPositionLabel to toPositionLabel) or a semicolon-separated set of commands
 *                ("wait 3", "emote 5" etc, see `execmovecmd') that will be executed after arrival.
 *                The commands don't have to end with ";moveon", it's executed in the end by default.
 */
function	script	setmovegraphcmd	{
    .@size = getarraysize(getvariableofnpc(.movepos_x1, strnpcinfo(3)));

    for (.@f = 0; .@f < getargcount();)
    {
        .@from   = findmovegraphlabel(getarg(.@f++));
        .@to     = findmovegraphlabel(getarg(.@f++));
        .@weight = 1;
        if (!isstr(getarg(.@f)))
            .@weight = getarg(.@f++);
        .@flags  = 0xffff;
        if (!isstr(getarg(.@f)))
            .@flags = getarg(.@f++);
        .@cmd$   = getarg(.@f++);
        .@index = .@from * .@size + .@to;  // emulation of 2d array
        set getvariableofnpc(.movegraphcmd$[.@index], strnpcinfo(3)), .@cmd$;
        set getvariableofnpc(.movegraphweight[.@index], strnpcinfo(3)), .@weight;
        set getvariableofnpc(.movegraphflags[.@index], strnpcinfo(3)), .@flags;
    }
    return;
}


function	script	execmovecmd	{

    explode(.@cmd$, getarg(0), " ");

    if (.@cmd$[0] == "moveon")
    {
        return 0;
    }
    else if (.@cmd$[0] == "dir")
    {
        .dir = atoi(.@cmd$[1]);
    }
    else if (.@cmd$[0] == "sit")
    {
        npcsit;
    }
    else if (.@cmd$[0] == "stand")
    {
        npcstand;
    }
    else if (.@cmd$[0] == "wait")
    {
        set getvariableofnpc(.waitticks, strnpcinfo(3)), atoi(.@cmd$[1]);
        return 1;
    }
    else if (.@cmd$[0] == "emote")
    {
        unitemote getnpcid(0), atoi(.@cmd$[1]);
    }
    else if (.@cmd$[0] == "class")
    {
        .class = atoi(.@cmd$[1]);
    }
    else if (.@cmd$[0] == "warp")
    {
        .@pos = -1;
        .@map$ = "";
        .@pos_idx = 1;
        if (getarraysize(.@cmd$) == 3)
        {
            .@map$ = .@cmd$[1];
            .@pos_idx = 2;
        }
        .@pos = findmovegraphlabel(.@cmd$[.@pos_idx]);
        if (.@pos > 0)
        {
            .@x = getvariableofnpc(.movepos_x1[.@pos], strnpcinfo(3));
            .@y = getvariableofnpc(.movepos_y1[.@pos], strnpcinfo(3));
            if (getstrlen(.@map$) > 0)
                unitwarp getnpcid(0), .@map$, .@x, .@y;
            else
                movenpc strnpcinfo(3), .@x, .@y;
            set getvariableofnpc(.movepos, strnpcinfo(3)), .@pos;
        }
        else
        {
            debugmes "execmovecmd: unknown WARP destination label: " + .@cmd$[1];
        }
    }
    else if (.@cmd$[0] == "call")
    {
        switch (getarraysize(.@cmd$))
        {
            case 1:
                debugmes "execmovecmd: CALL command needs some parameters";
                return 0;
            case 2:
                return callfunc(.@cmd$[1]);
                break;
            case 3:
                return callfunc(.@cmd$[1], .@cmd$[2]);
            case 4:
            default:
                return callfunc(.@cmd$[1], .@cmd$[2], .@cmd$[3]);
        }
    }
    else if (.@cmd$[0] == "speed")
    {
        .speed = atoi(.@cmd$[1]);
    }
    else if (.@cmd$[0] == "say")
    {
        deletearray .@cmd$[0], 1;
        npctalk implode(.@cmd$, " ");
    }
    else if (.@cmd$[0] == "flags")
    {
        set getvariableofnpc(.mg_flags, strnpcinfo(3)), axtoi(.@cmd$[1]);
    }
    else if (.@cmd$[0] == "flags_0")
    {
        .@flags = getvariableofnpc(.mg_flags, strnpcinfo(3));
        .@flags &= ~axtoi(.@cmd$[1]);
        set getvariableofnpc(.mg_flags, strnpcinfo(3)), .@flags;
    }
    else if (.@cmd$[0] == "flags_1")
    {
        .@flags = getvariableofnpc(.mg_flags, strnpcinfo(3));
        .@flags |= axtoi(.@cmd$[1]);
        set getvariableofnpc(.mg_flags, strnpcinfo(3)), .@flags;
    }
    else
    {
        debugmes "Unknown move graph cmd: " + .@cmd$[0];
    }
    return 0;
}


function	script	getnextmovecmd	{
    .@cmds$ = getvariableofnpc(.nextcmd$, strnpcinfo(3));
    .@firstCmd$ = .@cmds$;
    .@restCmd$ = "moveon";
    .@index = strpos(.@cmds$, ";");
    if (.@index >= 0)
    {
        .@firstCmd$ = substr(.@cmds$, 0, .@index - 1);
        .@restCmd$ = substr(.@cmds$, .@index + 1, getstrlen(.@cmds$) - 1);
    }
    // npcdebug "firstCmd = " + .@firstCmd$ + " restCmd = " + .@restCmd$;
    set getvariableofnpc(.nextcmd$, strnpcinfo(3)), .@restCmd$;
    return strip(.@firstCmd$);
}


// getrandompoint(x1,y1,x2,y2)
//    -- Get a random walkable point within a map rectangle
//    x1, y1 -- top-left corner of rectangle
//    x2, y2 -- bottom-right corner of rectangle
//  Returns 0 on success and -1 on error;
//  Since we cannot return multiple values, the random
//  coordinates are stored in NPC variables .move__rand_x, .move__rand_y
function	script	getrandompoint	{
    if (getargcount() < 4)
    {
        debugmes "error: getrandompoint(x1, y1, x2, y2) takes 4 arguments";
        return -1;
    }

    .@max_pokes = 10;
    .@x1 = getarg(0);
    .@y1 = getarg(1);
    .@x2 = getarg(2);
    .@y2 = getarg(3);
    .@rx = -1; .@ry = -1;

    getmapxy(.@map$, .@cx, .@cy, 1);  // npc location

    // let's try max_pokes random cells
    for (.@poke = 0; .@poke < .@max_pokes; .@poke++)
    {
        .@rx = rand(.@x1, .@x2);
        .@ry = rand(.@y1, .@y2);
        if (checknpccell(.@map$, .@rx, .@ry, cell_chkpass))
            goto L_Found;
    }

    // we check each cell from random middle point to the end
    for (;.@rx <= .@x2; .@rx++)
    {
        for (;.@ry <= .@y2; .@ry++)
            if (checknpccell(.@map$, .@rx, .@ry, cell_chkpass))
                goto L_Found;
        .@ry = .@y1;
    }

    // we check the rectangle from beginning to end
    for (.@rx = .@x1; .@rx <= .@x2; .@rx++)
        for (.@ry = .@y1; .@ry <= .@y2; .@ry++)
            if (checknpccell(.@map$, .@rx, .@ry, cell_chkpass))
                goto L_Found;

    // finally, if we don't find anything
    debugmes "error: getrandompoint: cannot find walkable cell in rectangle [(" + .@x1 + ","  + .@y1 + ") , (" + .@x2 + "," + .@y2 + ")]";
    return -1;

L_Found:
    set getvariableofnpc(.move__rand_x, strnpcinfo(3)), .@rx;
    set getvariableofnpc(.move__rand_y, strnpcinfo(3)), .@ry;
    return 0;
}


function	script	movetonextpoint	{
    .@wait = getvariableofnpc(.waitticks, strnpcinfo(3));
    if (.@wait > 0)
    {
        .@wait--;
        set getvariableofnpc(.waitticks, strnpcinfo(3)), .@wait;
        return;
    }

    .@nextcmd$ = "";
    while (.@nextcmd$ != "moveon")
    {
        .@nextcmd$ = getnextmovecmd();
        if (execmovecmd(.@nextcmd$))
            return;
    }

    // choose a random path from all possible paths
    .@size = getarraysize(getvariableofnpc(.movepos_x1, strnpcinfo(3)));
    .@pos = getvariableofnpc(.movepos, strnpcinfo(3));
    .@curr_flags = getvariableofnpc(.mg_flags, strnpcinfo(3));
    .@cur = 0;
    .@weight_sum = 0;
    // .@dbg$ = getvariableofnpc(.movegraphlabels$[.@pos], strnpcinfo(3)) + ": ";

    for (.@i = 0; .@i < .@size; .@i++)
    {
        .@index = .@pos * .@size + .@i;
        .@cmd$ = getvariableofnpc(.movegraphcmd$[.@index], strnpcinfo(3));
        .@flags = getvariableofnpc(.movegraphflags[.@index], strnpcinfo(3));
        if (.@cmd$ != "" &&
            .@curr_flags & .@flags)
        {
            .@nextpos[.@cur] = .@i;
            .@weights[.@cur] = getvariableofnpc(.movegraphweight[.@index], strnpcinfo(3));
            // .@dbg$ += getvariableofnpc(.movegraphlabels$[.@i], strnpcinfo(3)) + "=" + .@weights[.@cur] + " ";
            .@weight_sum += .@weights[.@cur];
            .@cur++;
        }
    }
    // npcdebug .@dbg$;

    if (!.@weight_sum)
    {
        npcdebug "error: cannot pick next walk point. flags=" + getvariableofnpc(.mg_flags, strnpcinfo(3));
        return;
    }

    // pick a random number based on weight_sum
    .@rnd = rand(.@weight_sum);
    .@k = -1; .@weight_sum = 0;
    while (.@rnd >= .@weight_sum)
    {
        .@k++;
        .@weight_sum += .@weights[.@k];
    }

    .@next_idx = .@nextpos[.@k];
    .@next_x1 = getvariableofnpc(.movepos_x1[.@next_idx], strnpcinfo(3));
    .@next_y1 = getvariableofnpc(.movepos_y1[.@next_idx], strnpcinfo(3));
    .@next_x2 = getvariableofnpc(.movepos_x2[.@next_idx], strnpcinfo(3));
    .@next_y2 = getvariableofnpc(.movepos_y2[.@next_idx], strnpcinfo(3));
    .@nextcmd$ = getvariableofnpc(.movegraphcmd$[.@pos * .@size + .@next_idx], strnpcinfo(3));
    set getvariableofnpc(.nextcmd$, strnpcinfo(3)), .@nextcmd$;
    set getvariableofnpc(.movepos, strnpcinfo(3)), .@next_idx;
    if (.@next_x2 > 0 && .@next_y2 > 0)
    {
        if (!getrandompoint(.@next_x1, .@next_y1, .@next_x2, .@next_y2))
        {
            .@next_x1 = getvariableofnpc(.move__rand_x, strnpcinfo(3));
            .@next_y1 = getvariableofnpc(.move__rand_y, strnpcinfo(3));
        }
    }
    if (getvariableofnpc(.debug, strnpcinfo(3)))
    {
        getmapxy(.@map$, .@cx, .@cy, 1);
        .@dist = distance(.@cx, .@cy, .@next_x1, .@next_y1);
        npcdebug "moving to " + getvariableofnpc(.movegraphlabels$[.@next_idx], strnpcinfo(3)) + " ("+.@next_x1 + "," + .@next_y1 + ") [distance=" + .@dist + "] flags=" + getvariableofnpc(.mg_flags, strnpcinfo(3));
    }
    set getvariableofnpc(.move_target_x, strnpcinfo(3)), .@next_x1;
    set getvariableofnpc(.move_target_y, strnpcinfo(3)), .@next_y1;
    npcwalkto .@next_x1, .@next_y1;
    return;
}


// initial actions for npc when using move graphs.
// function can accept 2 arguments:
//    1: action sequence, for example "speed 200; dir 4". Default is "moveon"
//    2: start point label. Default is #0 from move graph labels
function	script	firstmove	{
    .@nextcmd$ = getarg(0, "moveon");
    .@initpos = findmovegraphlabel(getarg(1, ""));
    if (.@initpos < 0) .@initpos = 0;

    set getvariableofnpc(.movepos, strnpcinfo(3)), .@initpos;
    movenpc strnpcinfo(3), getvariableofnpc(.movepos_x1[.@initpos], strnpcinfo(3)),
                           getvariableofnpc(.movepos_y1[.@initpos], strnpcinfo(3));
    set getvariableofnpc(.nextcmd$, strnpcinfo(3)), .@nextcmd$;
    set getvariableofnpc(.waitticks, strnpcinfo(3)), -1;
    set getvariableofnpc(.mg_flags, strnpcinfo(3)), 0xffff;
    movetonextpoint;
    return;
}


function	script	npc_pausemove	{
    stopnpctimer;
    .@move_after = 0;

    if (isunitwalking())
    {
        .@move_after = 1;
        npcwalkto .x, .y;
        npcstop;
    }
    set getvariableofnpc(.move_after_pause, strnpcinfo(3)), .@move_after;

    return 0;
}


function	script	npc_resumemove	{
    startnpctimer;

    if (getvariableofnpc(.move_after_pause, strnpcinfo(3)))
    {
        .@x = getvariableofnpc(.move_target_x, strnpcinfo(3));
        .@y = getvariableofnpc(.move_target_y, strnpcinfo(3));
        npcwalkto .@x, .@y;
    }

    return 0;
}


// npc_turntoxy(x,y)
// turn npc toward an object at position (x,y)
function	script	npc_turntoxy	{
    .@target_x = getarg(0);
    .@target_y = getarg(1);
    .@dx = abs(.@target_x - .x);
    .@dy = abs(.@target_y - .y);

    if (.@dx > .@dy)
        .dir = .@target_x >= .x ? 6 : 2;
    else
        .dir = .@target_y >= .y ? 0 : 4;

    return 0;
}

function	script	dographmovestep	{
    if (!isunitwalking())
    {
        movetonextpoint;
    }
    initnpctimer;
    end;
}
